use super::{
    err, err_value, json, ok, throw_err, Base, Client, ECode, Lock, MapSv, Resp, TBase, VBase,
    VMap, Value, MSV,
};
use base::tbase::log_stock;
use tracing::{instrument, warn};

pub struct Part;

impl Base for Part {}
impl TBase for Part {}

impl Part {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, client: &Client, lock: Lock) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        match cmd {
            "pick" => self.do_pick(line, data, client, lock).await,
            "add" => self.do_add(line, data, client, lock).await,
            "load" => self.do_load(line, data, client).await,
            _ => throw_err!(ECode::NoServe, "no part serve"),
        }
    }

    #[instrument(skip_all, name = "do_pick")]
    async fn do_pick(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let sn = data.ck_str("k")?;
        let num = data.ck_i64("num")?;
        let pname = data.ck_str("pname").unwrap_or("");

        let cmd = json!({ "line": line, "k": sn });

        let body = client.cfgdb(&cmd, "part/get").await?;
        let body = body.ck_obj("data")?;

        let dnum = body.ck_i64("dnum")?;

        let left = dnum - num;

        if left <= 0 {
            client.cfgdb(&cmd, "part/rm").await?;
        } else {
            let cmd = json!({
                "line": line,
                "v": {
                    "sn": sn,
                    "dnum": left,
                }
            });
            client.cfgdb(&cmd, "part/mditem").await?;
        }

        let mut body = body.clone();
        body.insert("num".to_string(), json!(num));
        body.insert("pname".to_string(), json!(pname));

        let _ = log_stock(line, "lgpart", sn, "out", &mut body, None, client, "").await;

        ok!()
    }

    #[instrument(skip_all, name = "do_add")]
    async fn do_add(&self, line: &str, para: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let tp = para.ck_str("tp")?;
        let mut val = para.ck_obj("v")?.clone();

        val.insert("tp".to_string(), json!(tp));

        warn!("add: get kind");

        let cmd = json!({ "line": line, "sn": tp });

        let body = client.cfgdb(&cmd, "file/get/kind").await?;
        let body = body.ck_obj("data")?;

        for (k, v) in body.iter() {
            if k == "sn" {
                val.insert("code".to_string(), v.clone());
                continue;
            }

            val.insert(k.to_string(), v.clone());
        }

        warn!("add: gen code");

        let cmd = json!({ "line": line, "rule": "part" });

        let body = client.cfgdb(&cmd, "code/gen").await?;
        let sn = body.ck_obj("data")?.ck_str("psn")?;

        val.insert("sn".to_string(), json!(sn));

        warn!("add: add part");

        let cmd = json!({ "line": line, "v": val });
        client.cfgdb(&cmd, "part/add").await?;

        let _ = log_stock(line, "lgpart", sn, "in", &mut val, None, client, "").await;

        ok!()
    }

    async fn do_load(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let tech = data.ck_str("tech")?;

        let body = self.get_part(line, client).await?;
        let body = body
            .get(tech)
            .ok_or(err_value(ECode::EmptyData, "no data"))?;

        ok!(body)
    }

    async fn get_part(&self, line: &str, client: &Client) -> Result<MSV, Value> {
        let cmd = json!({ "line": line });
        let mut ret = MSV::new();

        let body = client.cfgdb(&cmd, "part/list").await?;
        let body = body.ck_array("data")?;

        for d in body.iter() {
            let tech = match d.k_str("tech") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            match ret.get_mut(tech) {
                Some(v) => {
                    v.push(d.clone());
                }
                None => {
                    ret.insert(tech.to_string(), vec![d.clone()]);
                }
            }
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        Ok(ret)
    }
}
